/*
 * Copyright 2010 Alibaba Group Holding Limited.
 * All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * @(#)Configuration.java   1.11 2000/08/16
 *
 */

package org.w3c.tidy;

/**
 *
 * Read configuration file and manage configuration properties.
 *
 * (c) 1998-2000 (W3C) MIT, INRIA, Keio University
 * See Tidy.java for the copyright notice.
 * Derived from <a href="http://www.w3.org/People/Raggett/tidy">
 * HTML Tidy Release 4 Aug 2000</a>
 *
 * @author Dave Raggett <dsr@w3.org>
 * @author Andy Quick <ac.quick@sympatico.ca> (translation to Java)
 * @version 1.0, 1999/05/22
 * @version 1.0.1, 1999/05/29
 * @version 1.1, 1999/06/18 Java Bean
 * @version 1.2, 1999/07/10 Tidy Release 7 Jul 1999
 * @version 1.3, 1999/07/30 Tidy Release 26 Jul 1999
 * @version 1.4, 1999/09/04 DOM support
 * @version 1.5, 1999/10/23 Tidy Release 27 Sep 1999
 * @version 1.6, 1999/11/01 Tidy Release 22 Oct 1999
 * @version 1.7, 1999/12/06 Tidy Release 30 Nov 1999
 * @version 1.8, 2000/01/22 Tidy Release 13 Jan 2000
 * @version 1.9, 2000/06/03 Tidy Release 30 Apr 2000
 * @version 1.10, 2000/07/22 Tidy Release 8 Jul 2000
 * @version 1.11, 2000/08/16 Tidy Release 4 Aug 2000
 */

/*
 Configuration files associate a property name with a value.
 The format is that of a Java .properties file.
 */

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Properties;
import java.util.StringTokenizer;

public class Configuration implements java.io.Serializable {

    /**
     *
     */
    private static final long serialVersionUID = 7370280176303390368L;
    /* character encodings */
    public static final  int  RAW              = 0;
    public static final  int  ASCII            = 1;
    public static final  int  LATIN1           = 2;
    public static final  int  UTF8             = 3;
    public static final  int  ISO2022          = 4;
    public static final  int  MACROMAN         = 5;

    /* mode controlling treatment of doctype */
    public static final int DOCTYPE_OMIT   = 0;
    public static final int DOCTYPE_AUTO   = 1;
    public static final int DOCTYPE_STRICT = 2;
    public static final int DOCTYPE_LOOSE  = 3;
    public static final int DOCTYPE_USER   = 4;

    protected int spaces       = 2; /*
                               * default indentation
                               */
    protected int wraplen      = 68; /*
                                 * default wrap margin
                                 */
    protected int CharEncoding = ASCII;
    protected int tabsize      = 4;

    protected int     docTypeMode = DOCTYPE_AUTO; /*
                                               * see doctype property
                                               */
    protected String  altText     = null; /*
                                      * default text for alt attribute
                                      */
    protected String  slidestyle  = null; /*
                                         * style sheet for slides
                                         */
    protected String  docTypeStr  = null; /*
                                         * user specified doctype
                                         */
    protected String  errfile     = null; /*
                                      * file name to write errors to
                                      */
    protected boolean writeback   = false; /*
                                          * if true then output tidied markup
                                          */

    protected boolean OnlyErrors       = false; /*
                                           * if true normal output is suppressed
                                           */
    protected boolean ShowWarnings     = true; /*
                                            * however errors are always shown
                                            */
    protected boolean Quiet            = false; /*
                                      * no 'Parsing X', guessed DTD or summary
                                      */
    protected boolean IndentContent    = false; /*
                                              * indent content of appropriate
                                              * tags
                                              */
    protected boolean SmartIndent      = false; /*
                                            * does text /block level content
                                            * effect indentation
                                            */
    protected boolean HideEndTags      = false; /*
                                            * suppress optional end tags
                                            */
    protected boolean XmlTags          = false; /*
                                        * treat input as XML
                                        */
    protected boolean XmlOut           = false; /*
                                       * create output as XML
                                       */
    protected boolean xHTML            = false; /*
                                      * output extensible HTML
                                      */
    protected boolean XmlPi            = false; /*
                                      * add <?xml?> for XML docs
                                      */
    protected boolean RawOut           = false; /*
                                       * avoid mapping values > 127 to entities
                                       */
    protected boolean UpperCaseTags    = false; /*
                                              * output tags in upper not lower
                                              * case
                                              */
    protected boolean UpperCaseAttrs   = false; /*
                                               * output attributes in upper not
                                               * lower case
                                               */
    protected boolean MakeClean        = false; /*
                                          * remove presentational clutter
                                          */
    protected boolean LogicalEmphasis  = false; /*
                                                * replace i by em and b by
                                                * strong
                                                */
    protected boolean DropFontTags     = false; /*
                                             * discard presentation tags
                                             */
    protected boolean DropEmptyParas   = true; /*
                                              * discard empty p elements
                                              */
    protected boolean FixComments      = true; /*
                                           * fix comments with adjacent hyphens
                                           */
    protected boolean BreakBeforeBR    = false; /*
                                              * o/p newline before <br> or not?
                                              */
    protected boolean BurstSlides      = false; /*
                                            * create slides on each h2 element
                                            */
    protected boolean NumEntities      = false; /*
                                            * use numeric entities
                                            */
    protected boolean QuoteMarks       = false; /*
                                           * output " marks as &quot;
                                           */
    protected boolean QuoteNbsp        = true; /*
                                         * output non -breaking space as entity
                                         */
    protected boolean QuoteAmpersand   = true; /*
                                              * output naked ampersand as &amp;
                                              */
    protected boolean WrapAttVals      = false; /*
                                            * wrap within attribute values
                                            */
    protected boolean WrapScriptlets   = false; /*
                                               * wrap within JavaScript string
                                               * literals
                                               */
    protected boolean WrapSection      = true; /*
                                           * wrap within <![ ... ]> section tags
                                           */
    protected boolean WrapAsp          = true; /*
                                       * wrap within ASP pseudo elements
                                       */
    protected boolean WrapJste         = true; /*
                                        * wrap within JSTE pseudo elements
                                        */
    protected boolean WrapPhp          = true; /*
                                       * wrap within PHP pseudo elements
                                       */
    protected boolean FixBackslash     = true; /*
                                            * fix URLs by replacing \ with /
                                            */
    protected boolean IndentAttributes = false; /*
                                                 * newline+ indent before each
                                                 * attribute
                                                 */
    protected boolean XmlPIs           = false; /*
                                       * if set to yes PIs must end with ?>
                                       */
    protected boolean XmlSpace         = false; /*
                                         * if set to yes adds xml :space attr as
                                         * needed
                                         */
    protected boolean EncloseBodyText  = false; /*
                                                * if yes text at body is wrapped
                                                * in <p>'s
                                                */
    protected boolean EncloseBlockText = false; /*
                                                 * if yes text in blocks is
                                                 * wrapped in <p>'s
                                                 */
    protected boolean KeepFileTimes    = true; /*
                                             * if yes last modied time is
                                             * preserved
                                             */
    protected boolean Word2000         = false; /*
                                         * draconian cleaning for Word2000
                                         */
    protected boolean TidyMark         = true; /*
                                        * add meta element indicating tidied doc
                                        */
    protected boolean Emacs            = false; /*
                                      * if true format error output for GNU
                                      * Emacs
                                      */
    protected boolean LiteralAttribs   = false; /*
                                               * if true attributes may use
                                               * newlines
                                               */

    protected TagTable tt; /*
                            * TagTable associated with this Configuration
                            */

    private transient Properties _properties = new Properties();

    public Configuration() {
    }

    public void addProps(Properties p) {
        Enumeration e = p.propertyNames();
        while (e.hasMoreElements()) {
            String key = (String) e.nextElement();
            String value = p.getProperty(key);
            _properties.put(key, value);
        }
        parseProps();
    }

    public void parseFile(String filename) {
        try {
            _properties.load(new FileInputStream(filename));
        } catch (IOException e) {
            System.err.println(filename + e.toString());
            return;
        }
        parseProps();
    }

    private void parseProps() {
        String value;

        value = _properties.getProperty("indent-spaces");
        if (value != null) {
            spaces = parseInt(value, "indent-spaces");
        }

        value = _properties.getProperty("wrap");
        if (value != null) {
            wraplen = parseInt(value, "wrap");
        }

        value = _properties.getProperty("wrap-attributes");
        if (value != null) {
            WrapAttVals = parseBool(value, "wrap-attributes");
        }

        value = _properties.getProperty("wrap-script-literals");
        if (value != null) {
            WrapScriptlets = parseBool(value, "wrap-script-literals");
        }

        value = _properties.getProperty("wrap-sections");
        if (value != null) {
            WrapSection = parseBool(value, "wrap-sections");
        }

        value = _properties.getProperty("wrap-asp");
        if (value != null) {
            WrapAsp = parseBool(value, "wrap-asp");
        }

        value = _properties.getProperty("wrap-jste");
        if (value != null) {
            WrapJste = parseBool(value, "wrap-jste");
        }

        value = _properties.getProperty("wrap-php");
        if (value != null) {
            WrapPhp = parseBool(value, "wrap-php");
        }

        value = _properties.getProperty("literal-attributes");
        if (value != null) {
            LiteralAttribs = parseBool(value, "literal-attributes");
        }

        value = _properties.getProperty("tab-size");
        if (value != null) {
            tabsize = parseInt(value, "tab-size");
        }

        value = _properties.getProperty("markup");
        if (value != null) {
            OnlyErrors = parseInvBool(value, "markup");
        }

        value = _properties.getProperty("quiet");
        if (value != null) {
            Quiet = parseBool(value, "quiet");
        }

        value = _properties.getProperty("tidy-mark");
        if (value != null) {
            TidyMark = parseBool(value, "tidy-mark");
        }

        value = _properties.getProperty("indent");
        if (value != null) {
            IndentContent = parseIndent(value, "indent");
        }

        value = _properties.getProperty("indent-attributes");
        if (value != null) {
            IndentAttributes = parseBool(value, "ident-attributes");
        }

        value = _properties.getProperty("hide-endtags");
        if (value != null) {
            HideEndTags = parseBool(value, "hide-endtags");
        }

        value = _properties.getProperty("input-xml");
        if (value != null) {
            XmlTags = parseBool(value, "input-xml");
        }

        value = _properties.getProperty("output-xml");
        if (value != null) {
            XmlOut = parseBool(value, "output-xml");
        }

        value = _properties.getProperty("output-xhtml");
        if (value != null) {
            xHTML = parseBool(value, "output-xhtml");
        }

        value = _properties.getProperty("add-xml-pi");
        if (value != null) {
            XmlPi = parseBool(value, "add-xml-pi");
        }

        value = _properties.getProperty("add-xml-decl");
        if (value != null) {
            XmlPi = parseBool(value, "add-xml-decl");
        }

        value = _properties.getProperty("assume-xml-procins");
        if (value != null) {
            XmlPIs = parseBool(value, "assume-xml-procins");
        }

        value = _properties.getProperty("raw");
        if (value != null) {
            RawOut = parseBool(value, "raw");
        }

        value = _properties.getProperty("uppercase-tags");
        if (value != null) {
            UpperCaseTags = parseBool(value, "uppercase-tags");
        }

        value = _properties.getProperty("uppercase-attributes");
        if (value != null) {
            UpperCaseAttrs = parseBool(value, "uppercase-attributes");
        }

        value = _properties.getProperty("clean");
        if (value != null) {
            MakeClean = parseBool(value, "clean");
        }

        value = _properties.getProperty("logical-emphasis");
        if (value != null) {
            LogicalEmphasis = parseBool(value, "logical-emphasis");
        }

        value = _properties.getProperty("word-2000");
        if (value != null) {
            Word2000 = parseBool(value, "word-2000");
        }

        value = _properties.getProperty("drop-empty-paras");
        if (value != null) {
            DropEmptyParas = parseBool(value, "drop-empty-paras");
        }

        value = _properties.getProperty("drop-font-tags");
        if (value != null) {
            DropFontTags = parseBool(value, "drop-font-tags");
        }

        value = _properties.getProperty("enclose-text");
        if (value != null) {
            EncloseBodyText = parseBool(value, "enclose-text");
        }

        value = _properties.getProperty("enclose-block-text");
        if (value != null) {
            EncloseBlockText = parseBool(value, "enclose-block-text");
        }

        value = _properties.getProperty("alt-text");
        if (value != null) {
            altText = value;
        }

        value = _properties.getProperty("add-xml-space");
        if (value != null) {
            XmlSpace = parseBool(value, "add-xml-space");
        }

        value = _properties.getProperty("fix-bad-comments");
        if (value != null) {
            FixComments = parseBool(value, "fix-bad-comments");
        }

        value = _properties.getProperty("split");
        if (value != null) {
            BurstSlides = parseBool(value, "split");
        }

        value = _properties.getProperty("break-before-br");
        if (value != null) {
            BreakBeforeBR = parseBool(value, "break-before-br");
        }

        value = _properties.getProperty("numeric-entities");
        if (value != null) {
            NumEntities = parseBool(value, "numeric-entities");
        }

        value = _properties.getProperty("quote-marks");
        if (value != null) {
            QuoteMarks = parseBool(value, "quote-marks");
        }

        value = _properties.getProperty("quote-nbsp");
        if (value != null) {
            QuoteNbsp = parseBool(value, "quote-nbsp");
        }

        value = _properties.getProperty("quote-ampersand");
        if (value != null) {
            QuoteAmpersand = parseBool(value, "quote-ampersand");
        }

        value = _properties.getProperty("write-back");
        if (value != null) {
            writeback = parseBool(value, "write-back");
        }

        value = _properties.getProperty("keep-time");
        if (value != null) {
            KeepFileTimes = parseBool(value, "keep-time");
        }

        value = _properties.getProperty("show-warnings");
        if (value != null) {
            ShowWarnings = parseBool(value, "show-warnings");
        }

        value = _properties.getProperty("error-file");
        if (value != null) {
            errfile = parseName(value, "error-file");
        }

        value = _properties.getProperty("slide-style");
        if (value != null) {
            slidestyle = parseName(value, "slide-style");
        }

        value = _properties.getProperty("new-inline-tags");
        if (value != null) {
            parseInlineTagNames(value, "new-inline-tags");
        }

        value = _properties.getProperty("new-blocklevel-tags");
        if (value != null) {
            parseBlockTagNames(value, "new-blocklevel-tags");
        }

        value = _properties.getProperty("new-empty-tags");
        if (value != null) {
            parseEmptyTagNames(value, "new-empty-tags");
        }

        value = _properties.getProperty("new-pre-tags");
        if (value != null) {
            parsePreTagNames(value, "new-pre-tags");
        }

        value = _properties.getProperty("char-encoding");
        if (value != null) {
            CharEncoding = parseCharEncoding(value, "char-encoding");
        }

        value = _properties.getProperty("doctype");
        if (value != null) {
            docTypeStr = parseDocType(value, "doctype");
        }

        value = _properties.getProperty("fix-backslash");
        if (value != null) {
            FixBackslash = parseBool(value, "fix-backslash");
        }

        value = _properties.getProperty("gnu-emacs");
        if (value != null) {
            Emacs = parseBool(value, "gnu-emacs");
        }
    }

    /* ensure that config is self consistent */
    public void adjust() {
        if (EncloseBlockText) {
            EncloseBodyText = true;
        }

        /* avoid the need to set IndentContent when SmartIndent is set */

        if (SmartIndent) {
            IndentContent = true;
        }

        /* disable wrapping */
        if (wraplen == 0) {
            wraplen = 0x7FFFFFFF;
        }

        /* Word 2000 needs o:p to be declared as inline */
        if (Word2000) {
            tt.defineInlineTag("o:p");
        }

        /* XHTML is written in lower case */
        if (xHTML) {
            XmlOut = true;
            UpperCaseTags = false;
            UpperCaseAttrs = false;
        }

        /* if XML in, then XML out */
        if (XmlTags) {
            XmlOut = true;
            XmlPIs = true;
        }

        /* XML requires end tags */
        if (XmlOut) {
            QuoteAmpersand = true;
            HideEndTags = false;
        }
    }

    private static int parseInt(String s, String option) {
        int i = 0;
        try {
            i = Integer.parseInt(s);
        } catch (NumberFormatException e) {
            Report.badArgument(option);
            i = -1;
        }
        return i;
    }

    private static boolean parseBool(String s, String option) {
        boolean b = false;
        if (s != null && s.length() > 0) {
            char c = s.charAt(0);
            if (c == 't' || c == 'T' || c == 'Y' || c == 'y' || c == '1') {
                b = true;
            } else if (c == 'f' || c == 'F' || c == 'N' || c == 'n' || c == '0') {
                b = false;
            } else {
                Report.badArgument(option);
            }
        }
        return b;
    }

    private static boolean parseInvBool(String s, String option) {
        boolean b = false;
        if (s != null && s.length() > 0) {
            char c = s.charAt(0);
            if (c == 't' || c == 'T' || c == 'Y' || c == 'y') {
                b = true;
            } else if (c == 'f' || c == 'F' || c == 'N' || c == 'n') {
                b = false;
            } else {
                Report.badArgument(option);
            }
        }
        return !b;
    }

    private static String parseName(String s, String option) {
        StringTokenizer t = new StringTokenizer(s);
        String rs = null;
        if (t.countTokens() >= 1) {
            rs = t.nextToken();
        } else {
            Report.badArgument(option);
        }
        return rs;
    }

    private static int parseCharEncoding(String s, String option) {
        int result = ASCII;

        if (Lexer.wstrcasecmp(s, "ascii") == 0) {
            result = ASCII;
        } else if (Lexer.wstrcasecmp(s, "latin1") == 0) {
            result = LATIN1;
        } else if (Lexer.wstrcasecmp(s, "raw") == 0) {
            result = RAW;
        } else if (Lexer.wstrcasecmp(s, "utf8") == 0) {
            result = UTF8;
        } else if (Lexer.wstrcasecmp(s, "iso2022") == 0) {
            result = ISO2022;
        } else if (Lexer.wstrcasecmp(s, "mac") == 0) {
            result = MACROMAN;
        } else {
            Report.badArgument(option);
        }

        return result;
    }

    /* slight hack to avoid changes to pprint.c */
    private boolean parseIndent(String s, String option) {
        boolean b = IndentContent;

        if (Lexer.wstrcasecmp(s, "yes") == 0) {
            b = true;
            SmartIndent = false;
        } else if (Lexer.wstrcasecmp(s, "true") == 0) {
            b = true;
            SmartIndent = false;
        } else if (Lexer.wstrcasecmp(s, "no") == 0) {
            b = false;
            SmartIndent = false;
        } else if (Lexer.wstrcasecmp(s, "false") == 0) {
            b = false;
            SmartIndent = false;
        } else if (Lexer.wstrcasecmp(s, "auto") == 0) {
            b = true;
            SmartIndent = true;
        } else {
            Report.badArgument(option);
        }
        return b;
    }

    private void parseInlineTagNames(String s, String option) {
        StringTokenizer t = new StringTokenizer(s, " \t\n\r,");
        while (t.hasMoreTokens()) {
            tt.defineInlineTag(t.nextToken());
        }
    }

    private void parseBlockTagNames(String s, String option) {
        StringTokenizer t = new StringTokenizer(s, " \t\n\r,");
        while (t.hasMoreTokens()) {
            tt.defineBlockTag(t.nextToken());
        }
    }

    private void parseEmptyTagNames(String s, String option) {
        StringTokenizer t = new StringTokenizer(s, " \t\n\r,");
        while (t.hasMoreTokens()) {
            tt.defineEmptyTag(t.nextToken());
        }
    }

    private void parsePreTagNames(String s, String option) {
        StringTokenizer t = new StringTokenizer(s, " \t\n\r,");
        while (t.hasMoreTokens()) {
            tt.definePreTag(t.nextToken());
        }
    }

    /*
     * doctype: omit | auto | strict | loose | <fpi> where the fpi is a string
     * similar to "-//ACME//DTD HTML 3.14159//EN"
     */
    protected String parseDocType(String s, String option) {
        s = s.trim();

        /* "-//ACME//DTD HTML 3.14159//EN" or similar */

        if (s.startsWith("\"")) {
            docTypeMode = DOCTYPE_USER;
            return s;
        }

        /* read first word */
        String word = "";
        StringTokenizer t = new StringTokenizer(s, " \t\n\r,");
        if (t.hasMoreTokens()) {
            word = t.nextToken();
        }

        if (Lexer.wstrcasecmp(word, "omit") == 0) {
            docTypeMode = DOCTYPE_OMIT;
        } else if (Lexer.wstrcasecmp(word, "strict") == 0) {
            docTypeMode = DOCTYPE_STRICT;
        } else if (Lexer.wstrcasecmp(word, "loose") == 0 || Lexer.wstrcasecmp(word, "transitional") == 0) {
            docTypeMode = DOCTYPE_LOOSE;
        } else if (Lexer.wstrcasecmp(word, "auto") == 0) {
            docTypeMode = DOCTYPE_AUTO;
        } else {
            docTypeMode = DOCTYPE_AUTO;
            Report.badArgument(option);
        }
        return null;
    }
}
